package ch02.com.foed
{
	/**
	 * 转向机车(SteeredVehicle)类
	 * @author jianping.shenjp
	 */
	import flash.display.Sprite;

	public class SteeredVehicle extends Vehicle
	{
		/**
		 * _steeringForce属性，它是一个2D向量。该属性作为每个行为叠加后的转向合力。
		 * 同时留意此处还有一个_maxForce属性，因为现实中不会有什么旋转是一瞬间完成的，
		 * 所以要对旋转力加以限制，使其在一帧里的大小不会太离谱。
		 * 可以通过公开的getter/setter函数 maxForce读写。
		 * 对maxForce的修正，会使机车旋转更急剧，移动更快速，走位更准确，
		 * 或是出现一个很牛x的大甩尾。
		 */
		private var _maxForce:Number = 1;
		private var _arrivalThreshold:Number = 100; //到达行为，开始减速的阈值
		private var _steeringForce:Vector2D;

		private var _wanderAngle:Number = 0; //漫游的角度
		private var _wanderDistance:Number = 10; //漫游的距离
		private var _wanderRadius:Number = 5; //漫游的半径
		private var _wanderRange:Number = 1; //漫游的范围

		/**
		 * 对象回避
		 */
		private var _avoidDistance:Number = 300; //回避距离(_avoidDistance)意指，发现障碍物的有效视野
		private var _avoidBuffer:Number = 20; //回避缓冲(_avoidBuffer) 意指，机车在准备避开时，自身和障碍物间的预留距离

		/**
		 * 路径跟随
		 */
		//路径索引(pathIndex)相当于是数组索引，用于指向下一个航点。路径阈值(pathThreshold)相当于航点间距。
		private var _pathIndex:int = 0;
		private var _pathThreshold:Number = 20;

		/**
		 * 群落
		 */
		private var _inSightDist:Number = 200;
		private var _tooCloseDist:Number = 60;

		public function SteeredVehicle(color:uint = 0x000000)
		{
			_steeringForce = new Vector2D();
			super(color);
		}

		public function set maxForce(value:Number):void
		{
			_maxForce = value;
		}

		public function get maxForce():Number
		{
			return _maxForce;
		}

		public function set arriveThreshold(value:Number):void
		{
			_arrivalThreshold = value;
		}

		public function get arriveThreshold():Number
		{
			return _arrivalThreshold;
		}

		public function set wanderDistance(value:Number):void
		{
			_wanderDistance = value;
		}

		public function get wanderDistance():Number
		{
			return _wanderDistance;
		}

		public function set wanderRadius(value:Number):void
		{
			_wanderRadius = value;
		}

		public function get wanderRadius():Number
		{
			return _wanderRadius;
		}

		public function set wanderRange(value:Number):void
		{
			_wanderRange = value;
		}

		public function get wanderRange():Number
		{
			return _wanderRange;
		}

		public function get pathIndex():int
		{
			return _pathIndex;
		}

		public function set pathIndex(value:int):void
		{
			_pathIndex = value;
		}

		public function get pathThreshold():Number
		{
			return _pathThreshold;
		}

		public function set pathThreshold(value:Number):void
		{
			_pathThreshold = value;
		}

		public function get inSightDist():Number
		{
			return _inSightDist;
		}

		public function set inSightDist(value:Number):void
		{
			_inSightDist = value;
		}

		public function get tooCloseDist():Number
		{
			return _tooCloseDist;
		}

		public function set tooCloseDist(value:Number):void
		{
			_tooCloseDist = value;
		}

		override public function update():void
		{
			/**
			 * _steeringForce属性即是一个有意义的向量。
			 * 第一个 truncate（截断）函数是不让_steeringForce超过最大作用力。
			 * 然后除以(divide)机车的质量(mass)。在现实中，越重的物 体有着越大的动力，
			 * 旋转的角度也越大，而较轻的物体则旋转更快速。
			 * 接着把转向力叠加于机车的当前速度上，再把_steeringForce设回零向量，
			 * 以便于下一轮作用力的叠加。
			 */
			//不让_steeringForce超过最大作用力。
			_steeringForce.truncate(_maxForce);
			//除以(divide)机车的质量(mass)。在现实中，越重的物 体有着越大的动力，旋转的角度也越大，而较轻的物体则旋转更快速。F=ma
			_steeringForce = _steeringForce.divide(_mass);
			//把转向力叠加于机车的当前速度上，再把_steeringForce设回零向量，以 便于下一轮作用力的叠加。
			_velocity = _velocity.add(_steeringForce);
			_steeringForce = new Vector2D();
			super.update();
		}

		/**
		 * 寻找行为
		 * @param	target
		 */
		public function seek(target:Vector2D):void
		{
			/**
			 * 通过目标位置和机车位置相减，计算出一个能使机车准确到达目标的期望速度。
			 * 这个速度向量的含义是“如果想到达目的地，需要以如此方向，移动这般快才行”。
			 *
			 * 现实不会让你想去哪儿就能一下子过去的。为此，多尝试几次后会找出一个不错的算法。
			 * 在这里，我们通过单位化期望速度并乘以最大速率来实现。
			 * 这样做，仍然能得到一个指向目标的向量，其含义则是“不要想着会瞬间移动，
			 * 以最快速度向正确方向迈进吧。”
			 */
			var desiredVelocity:Vector2D = target.subtract(_position);
			desiredVelocity.normalize();
			desiredVelocity = desiredVelocity.multiply(_maxSpeed);
			/**
			 * 有了期望速度，显然还要考虑机车现有的速度。
			 * 通过两者相减，得到一个向量，其含义是“尽最大可能以最大速率面向正确方位。”
			 */
			var force:Vector2D = desiredVelocity.subtract(_velocity);
			trace(desiredVelocity, _velocity, force);
			/**
			 * 这个向量还会叠加给转向力。记得update函数中，_steeringForce总是被限制在最大力度以内。
			 * 所以，虽然仍旧没有朝着想要的准确方向走，却在最大力度和最大速率的限制下尽了最大的可能。
			 */
			_steeringForce = _steeringForce.add(force);
		}

		/**
		 * 避开行为
		 * 避开行为与寻找行为彻底相反。实际上，除了代码最后一行用相减代替了相加以外，其它都一样。
		 * @param	target
		 */
		public function flee(target:Vector2D):void
		{
			var desiredVelocity:Vector2D = target.subtract(_position);
			desiredVelocity.normalize();
			desiredVelocity = desiredVelocity.multiply(_maxSpeed);
			var force:Vector2D = desiredVelocity.subtract(_velocity);
			_steeringForce = _steeringForce.subtract(force);
		}

		/**
		 * 到达行为
		 * 到达行为在很多场合都可以被当作是寻找行为。
		 * 实际上，它们之间的算法和处理方式都一样。
		 * 唯一不同的是，在到达模式中，一辆机车在到达目标的某一距离时，
		 * 会变成一种精确模式慢慢地靠近目标点。
		 * 为了了解到达行为的必要性，可以先运行一下SeekTest类，
		 * 然后移动鼠标到某处让机车过来“抓住”它。会看到机车快速的越过了鼠标，
		 * 接着它发现过头了，又返回来，还是过头了....于是会一直循环下去。
		 * 这是因为机车始终保持着最大速度迈向目标，哪怕离目标只有几像素。
		 *	到达行为通过减速接近目标，解决了这个问题：
		 * @param	target
		 */
		public function arrive(target:Vector2D):void
		{
			/**
			 * 程序一开始和寻找行为一样。但是在期望速度乘以最大速率时，做了距离检测。
			 * 如果距离大于某个值，那一切照旧。程序往后走，接着的事情也和寻找一样。
			 * 关键是，距离小于某个值时所做的事情。
			 * 本来乘以_maxSpeed现改为乘以_maxSpeed * dist / _arriveThreshold。
			 * 如果距离仅仅小于某个值一点点，那么dist / _arriveThreshold会非常接近1.0，
			 * 可能是0.99。因此，期望速度的大小也会非常接近于（略小于）最大速率。
			 * 如果距离接近0，那么得到 的比率也会非常非常小，期望速度改变也会很小。
			 * 最终速度会趋向于0（假设只有一个行为作用于该机车）。
			 */
			var desiredVelocity:Vector2D = target.subtract(_position);
			desiredVelocity.normalize();
			var dist:Number = _position.dist(target);
			if (dist > _arrivalThreshold)
			{
				desiredVelocity = desiredVelocity.multiply(_maxSpeed);
			}
			else
			{
				desiredVelocity = desiredVelocity.multiply(_maxSpeed * dist / _arrivalThreshold);
			}
			var force:Vector2D = desiredVelocity.subtract(_velocity);
			_steeringForce = _steeringForce.add(force);
		}

		/**
		 * 追捕行为
		 * 对于追捕行为，又要重复一遍“它非常类似寻找行为”。
		 * 实际上，追捕的最后一个动作就是调用寻找。追捕的本质是预测目标所在的位置。
		 * 这也暗示目标是一个移动对象，它也有位置和速度属性。
		 * 因此，假设目标是一辆机车，而它在大多数情况下是另一种继承机车类的转向机车。
		 * 那么，该如何预测目标的位置呢？其实是以目标的当前速度不变为前提，
		 * 算出未来一段时刻后目标所在的位置。但是需要计算多久的未来才合适呢？
		 * 问的好！我们把这 段时间叫做预测时间(look ahead time)。
		 * 如果算到很久以后（较长的预测时间）可能就会超越目标，
		 * 如果只往后算一点点（较短的预测时间）可能仍然落后于目标。
		 * 而实际上，寻找行为就是一 个预测时间为零的追捕行为（零秒后的未来？没错，就是此刻。）
		 * 一个策略是基于两机车间的距离来判断预测时间。
		 * 如果目标太远，需要花一段时间才能赶上，就预测长一点，
		 * 如果很接近，马上就能达到，就预测短一点。
		 * @param	target
		 */
		public function pursue(target:Vehicle):void
		{
			/**
			 * 首先通过两者间距离除以最大速率计算出预测时间。
			 * 这就得到了追上目标所需要的时间间隔（假设目标不再移动）。
			 * 通过目标的速度乘以时间间隔得到预测移动距离，再加到当前位置上就是预测位置。
			 * 最后，把这个预测位置作为寻找点。
			 *
			 * 需要注意，这个函数的结果很粗糙，因为这一切都是在运动中的。但对于单单寻找来说还是比较精确的。
			 * 该方法也比较简单快速，对于追捕还有别的更准确，也更复杂的算法，如果有兴趣可以自己研究。
			 * 这里所做的至少能为您开个头。
			 */
			var lookAheadTime:Number = position.dist(target.position) / _maxSpeed;
			var predictedTarget:Vector2D = target.position.add(target.velocity.multiply(lookAheadTime));
			seek(predictedTarget);
		}

		/**
		 * 躲避行为
		 * 就像追捕类似于寻找，躲避类似于避开。
		 * 躲避从本质上讲，是预测出机车将要去到的位置并远离它。在这里所有的原则都和追捕相
		 * 同。实际上，就连实现都几乎一模一样，除了最后一行用避开代替寻找。
		 * @param	target
		 */
		public function evade(target:Vehicle):void
		{
			var lookAheadTime:Number = position.dist(target.position) / _maxSpeed;
			var predictedTarget:Vector2D = target.position.add(target.velocity.multiply(lookAheadTime));
			flee(predictedTarget);
		}

		/**
		 * 漫游行为
		 * 漫游行为在实现上不像听起来那么容易。简单的使用随机而产生的布朗运动，会让角色感
		 * 觉像是一个有神经病的傻瓜。我们需要更自然更平滑的感觉。有个办法，通常 设想在角色前方
		 * 有个圆，然后把圆上任意一点作为目标，每次更新都向这个随机点移动。由于目标点总是落在
		 * 假象的圆上，所以转向力永远不会一下子就变化很大。 可以参照下图加以理解：图2-8
		 */
		public function wander():void
		{
			/**
			 * 一开始先通过单位化速度确定圆的中心点位于速度向量的正前方，然后乘以漫游距离，就
			 * 是圆心的所在地。接着增加另一个偏移量来确定随机点。由于该点落在圆上，所以偏移量的长
			 * 度等于圆的半径，偏移量的角度等于漫游角度。而漫游角度是根据漫游范围做适当的随机调
			 * 整。接着把偏移量加于中心点就得到了变化所需要的力度向量。最后把这个力度叠加到转向力
			 * 度上就完了。
			 */
			var center:Vector2D = velocity.clone().normalize().multiply(_wanderDistance);
			var offset:Vector2D = new Vector2D(0, 0);
			offset.length = _wanderRadius;
			offset.angle = _wanderAngle;
			_wanderAngle += Math.random() * _wanderRange - _wanderRange * .5;
			var force:Vector2D = center.add(offset);
			_steeringForce = _steeringForce.add(force);
		}

		/**
		 * 对象回避
		 * 对象回避不比迄今为止已经见识过的行为，它更为复杂，并且每个人实现的方式都不尽相
		   同，难易有别。所以对于这么一个没有严格统一标准的行为，这里给出的仅仅是很基本的实现
		   方式。我鼓励大家根据实际情况，为之做必要的改进。
		   对象回避主题的完整意义是指，在机车行走的路线中存在一些障碍物，机车必须绕开、防
		   止触碰到它们。听上去和碰撞检测有关，然而这仅仅是发生在预测阶段，也就是：“以我当前
		   的速度行驶下去，可能就会撞到它了。”
		   既然碰撞是预测的，就得长点脑子确保碰撞不会发生。你可能正幼稚的想，那停下来或者
		   调头不就行了嘛，你忘了有很多行为是在同时发生着的。如果要躲避的是一个食肉动物，光靠
		   停下来或者躲在树后面显然是不明智的。凡有脑子的，此时会采取一切手段来躲避，而食肉动
		   物也同样会绕开障碍物来追捕你。
		   另外，如果要避开一个非常接近的东西，就必须改变路线。可能在沙漠中，发现远处有座
		   金字塔，稍作调整甚至不作调整的继续前进都不会碰到它。而如果金字塔就在你面前，为了不
		   撞上去，起码要转差不多90 度左右。
		   现在了解了该行为的复杂程度，以及为什么存在那么多不同的实现方式了吧。在大多数解
		   决方案中，首先把障碍物看作是一个圆（3D 中是球）。实际上障碍物可能并不是圆，但为了计
		   算方便，还是把它们想象成一个有中心点和半径的对象。注意，通常情况下碰撞检测不需要严
		   格到像素级别，只要大致的知道其大小和位置即可，然后设法绕开它.

		   预测碰撞的代码是基于David M.Bourg 和Glenn Seemann 所著的《游戏开发中的人工智
		   能》的一个例子，不过是个粗糙的简易版。转向力的计算，则基于海量的源文件以及我自己的
		   想象。
		 * @param	circles
		 */
		public function avoid(circles:Array):void
		{
			for (var i:int = 0; i < circles.length; i++)
			{
				var circle:Circle = circles[i] as Circle;
				var heading:Vector2D = _velocity.clone().normalize();
				// 障碍物和机车间的位移向量
				var difference:Vector2D = circle.position.subtract(_position);
				var dotProd:Number = difference.dotProd(heading);
				// 如果障碍物在机车前方
				if (dotProd > 0)
				{
					// 机车的“触角”
					var feeler:Vector2D = heading.multiply(_avoidDistance);
					// 位移在触角上的映射
					var projection:Vector2D = heading.multiply(dotProd);
					// 障碍物离触角的距离
					var dist:Number = projection.subtract(difference).length;
					// 如果触角（在算上缓冲后）和障碍物相交
					// 并且位移的映射的长度小于触角的长度
					// 我们就说碰撞将要发生，需改变转向
					if (dist < circle.radius + _avoidBuffer && projection.length < feeler.length)
					{
						// 计算出一个转90 度的力
						var force:Vector2D = heading.multiply(_maxSpeed);
						force.angle += difference.sign(_velocity) * Math.PI / 2;
						// 通过离障碍物的距离，调整力度大小，使之足够小但又能避开
						force = force.multiply(1.0 - projection.length / feeler.length);
						// 叠加于转向力上
						_steeringForce = _steeringForce.add(force);
						// 刹车——转弯的时候要放慢机车速度，离障碍物越接近，刹车越狠。
						_velocity = _velocity.multiply(projection.length / feeler.length);
					}
				}
			}
		}

		/**
		 * 路径跟随
		   路径跟随这名字一听就知道要干嘛了：机车会沿着一个预定的路线行驶。虽然在地图或者
		   游戏中，路径是以图形的形式被表示的，而在转向行为中，其不过是一系列航点。在AS3 的实
		   现中，就是一个持有2D 向量的数组。
		   其策略真是简单到不行。只要从第一个航点开始挨个寻找下去即可。
		 * @param	path
		 * @param	loop
		 */
		public function followPath(path:Array, loop:Boolean = false):void
		{
			/**
			 * 一堆乱哄哄的判断条件。首先是取得当前航点，如果航点不是一个有效2D 向量，就返回，
			   这么作就是说，即使传递一个空数组也不会报错。接着是判断，到航点间的距离是否足以切换
			   到下一个航点，然后根据循环再判断最后一个航点索引是否归零。写起来是很啰嗦，实际上画
			   出航点后，按部就班的执行以上逻辑，就会发现是很直观的。
			   有了航点就能移动了，以此调用最后一行的寻找函数已经有了不俗表现，但为了更优美一
			   点，假设是最后一个航点又不要循环的话，采用到达行为可以使机车慢慢的靠近终点，而不会
			   在终点左右摇摆。
			 */
			var wayPoint:Vector2D = path[_pathIndex];
			if (wayPoint == null)
				return;
			if (_position.dist(wayPoint) < _pathThreshold)
			{
				if (_pathIndex >= path.length - 1)
				{
					if (loop)
					{
						_pathIndex = 0;
					}
				}
				else
				{
					_pathIndex++;
				}
			}
			if (_pathIndex >= path.length - 1 && !loop)
			{
				arrive(wayPoint);
			}
			else
			{
				seek(wayPoint);
			}
		}

		/**
		 * 群落
		 * 首先，鸟们都保持在同一个区域。如果有只鸟离队伍远了，就该马上归队。这叫凝聚。
		 * 其次，尽管鸟们都在一起飞，但是要避免不会互相碰到。为此，它们各自都有一个空间来
		   预防其它鸟太接近。这叫分离。
		   最后，鸟们飞行在同一个方向。当然各自的角度不一定相同，但是大方向是差不多的。这叫队
		   列。
		   这三个行为——凝聚、分离和队列——组成了复杂的群落行为。
		   当考虑鸟群时，就以整个群落是一条心去想象，或者认为每个鸟都充分认识群中的其它
		   鸟。我不想为此去争论什么，但我要说，当开始理解这三个行为，何以促成群落 行为时，你会
		   发现，每个鸟根本不需要知道多少东西，也不需要什么民主集中一条心来指挥群落。实际上，
		   每个鸟就只需要看看临近的几只伙伴。如果靠太近就离远 点，如果方向差太多就转过来点，最
		   终以此形成了传说中的群落行为。
		   尽管群落行为技术上被拆成了三个子行为，然而它们几乎总是捆绑出现的。一般不太会只
		   对角色使用其中一两个行为，所以就把这仨放于同一个函数中好了。这样效率也高，避免要做
		   三次循环。
		 * @param	vehicles
		 */
		public function flock(vehicles:Array):void
		{
			/**
			 * 首先，传递一个持有机车的数组。通过遍历这个数组找出进入视野的其它机车。把进入视
			   野的机车的速度和位置都加起来，然后统计次数，最后以此求得平均值。如果机车靠太近，用
			   避开函数离开之，以此实现分离。唯一要注意的地方就是处理过程中对自身的忽略。
			 */
			var averageVelocity:Vector2D = _velocity.clone();
			var averagePosition:Vector2D = new Vector2D();
			var inSightCount:int = 0;
			for (var i:int = 0; i < vehicles.length; i++)
			{
				var vehicle:Vehicle = vehicles[i] as Vehicle;
				if (vehicle != this && inSight(vehicle))
				{
					averageVelocity = averageVelocity.add(vehicle.velocity);
					averagePosition = averagePosition.add(vehicle.position);
					if (tooClose(vehicle))
					{
						flee(vehicle.position);
					}
					inSightCount++;
				}
			}
			if (inSightCount > 0)
			{
				averageVelocity = averageVelocity.divide(inSightCount);
				averagePosition = averagePosition.divide(inSightCount);
				seek(averagePosition);
				_steeringForce.add(averageVelocity.subtract(_velocity));
			}
		}

		/**
		 * 视野中
		 * inSight 函数判定一个机车是否能看到另一个机车。为此，先要检测两者间距离是否在视
		   野范围内，如果不是就返回false。接着用向量的数学运算判断机车的前后关 系，这里采用的
		   实现方式比较死板，只认前方的机车，在后面就当作看不见。这个做做例子够用了，如果要作
		   46
		   改进，可以先考虑做一个可变化的视野范围。窄的视野 范围意味着角色只能沿着视野方向，注
		   意不到两边，宽的视野意味着角色可以看到边上的一些东西。不同的视野范围，会导致不同的
		   群落模式。
		 * @param	vehicle
		 * @return
		 */
		public function inSight(vehicle:Vehicle):Boolean
		{
			if (_position.dist(vehicle.position) > _inSightDist)
			{
				return false;
			}
			var heading:Vector2D = _velocity.clone().normalize();
			var difference:Vector2D = vehicle.position.subtract(_position);
			var dotProd:Number = difference.dotProd(heading);

			if (dotProd < 0)
			{
				return false;
			}
			return true;
		}

		/**
		 * 太靠近
		 * @param	vehicle
		 * @return
		 */
		public function tooClose(vehicle:Vehicle):Boolean
		{
			return _position.dist(vehicle.position) < _tooCloseDist;
		}
	}
}